﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Xml;

using Vita.Common;
using Vita.Entities;
using Vita.Entities.Model.Construction;
using Vita.Data;
using Vita.Data.Upgrades;

namespace Vita.Tools.DbUpdate {
  public class DbUpdateProcessor {
    IProcessFeedback _feedback;
    DbUpdateConfig _config;

    public DbUpdateProcessor(IProcessFeedback feedback) {
      _feedback = feedback; 
    }

    public bool GenerateScripts(XmlDocument xmlConfig) {
      const string header =
@"-- DDL Scripts generated by VITA DB Tool. 
-- Generated on: {0}
-- Target database: {1}
-- Executed by user {2} on machine {3}.
";
      _config = new DbUpdateConfig(xmlConfig);
      Util.Check(File.Exists(_config.AssemblyPath), "Assembly file '{0}' not found.", _config.AssemblyPath);
      var asm = Assembly.LoadFrom(_config.AssemblyPath);
      var appType = asm.GetType(_config.AppClassName);
      Util.Check(appType != null, "Type {0} not found in target assembly.");
      // Using NonPublic flag to allow internal constructor; 
      // EntityApp must have a parameterless constructor, but it may be made internal, to hide from regular code
      var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance; 
      var appObj = Activator.CreateInstance(appType, flags, null, null, null);
      Util.Check(appObj != null, "Failed to create instance of class {0}.", _config.AppClassName);
      var entApp = appObj as EntityApp;
      Util.Check(entApp != null, "The target instance of class {0} is not an EntityApp instance.", _config.AppClassName);
      entApp.Init();
      var dbSettings = new DbSettings(_config.Driver, _config.DbOptions, _config.ConnectionString, 
                   upgradeMode: DbUpgradeMode.Always, upgradeOptions: _config.ModelUpdateOptions);
      var schemas = entApp.Areas.Select(a => a.Name).ToList();
      dbSettings.SetSchemas(schemas);
      var db = new Database(entApp, dbSettings);
      var ds = new DataSource(null, db); 
      var updateMgr = new DbUpgradeManager(ds);
      var upgrades = updateMgr.BuildUpgradeInfo();
      var ddl = string.Join(_config.Driver.DDLSeparator, upgrades.AllScripts.Select(scr => scr.Sql));
      if (string.IsNullOrEmpty(ddl))
        ddl = "-- (No changes detected)";
      var text = string.Format(header, DateTime.Now.ToString("s"), _config.ConnectionString, Environment.UserName, Environment.MachineName) + ddl; 
      File.WriteAllText(_config.OutputPath, text);
      _feedback.WriteLine(" Generated {0} scripts.", upgrades.AllScripts.Count);
      _feedback.WriteLine(" DDL scripts are saved to '{0}'", _config.OutputPath);
      return true;  
    }
  }
}
